using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using SlimDX;
using SlimDX.Direct3D9;


namespace Splino
{
    using Splino.D3D;

    /// <summary>
    /// The CommandManager.Instance class creates the command prompt
    /// and handles command line parsing and command execution
    /// </summary>
    public class CommandManager
    {
        /// <summary>
        /// A list of all active commands usable in Splino.
        /// If the command is not listed here, it will not be
        /// processed if entered in the command prompt.  If the 
        /// command is listed here but does have its own class
        /// definition, parsing the command will throw an exception
        /// </summary>
        string[] _commandList = new string[]
            {
                "PanCamera",
                "ZoomCamera",
                "RotateCamera",
                "UndoViewChange",
                "RedoViewChange",
                "Point",
                "Undo",
                "Redo",
                "UndoMultiple",
                "RedoMultiple",
                "Save",
                "SaveAs",
                "New",
                "Open",
                "Exit",
                "Select",
                "SelectAll",
                "SelectNone",
                "SelectInclusive",
                "SelectExclusive",
                "InvertSelection",
                "Delete",
                "CopyClipboard",
                "PasteClipboard",
                "CutClipboard",
                "GridSnap",
                "AngleSnap",
                "ObjectSnap",
                "ObjectEndSnap",
                "ObjectNearSnap",
                "ObjectPointSnap",
                "ObjectMidSnap",
                "ObjectCenterSnap",
                "ObjectIntersectSnap",
                "ObjectPerpSnap",
                "ObjectTanSnap",
                "ObjectQuadrantSnap",
                "GridSnapUnits",
                "AngleSnapDegrees",
                "LinearSpline",
                "NURBS",
                "Hide",
                "Show",
                "Lock",
                "Unlock",
                "Color",
                "HideControlPoints",
                "ShowControlPoints",
                "Move",
                "Scale1D",
                "Scale2D"
            };

        /// <summary>
        /// A sorted version of the _commandList string
        /// </summary>
        string[] _workingCommandList;

        string _outputText;
        /// <summary>
        /// Gets or sets the current output line to be 
        /// displayed in the Command Prompt
        /// </summary>
        public String OutputText
        {
            get { return _outputText; }
            set { _outputText = value; }
        }

        /// <summary>
        /// Gets an array of strings of all available commands
        /// </summary>
        public string[] CommandList
        {
            get { return _commandList; }
        }

        CommandPrompt _commandPrompt;
        /// <summary>
        /// Gets the form that displays the Command Prompt
        /// </summary>
        public CommandPrompt CommandPrompt
        {
            get { return _commandPrompt; }
        }

        bool _commandPromptActive = false;
        /// <summary>
        /// Gets or sets whether the Command Prompt is visible
        /// </summary>
        public bool CommandPromptActive
        {
            get { return _commandPromptActive; }
            set { _commandPromptActive = value; }
        }

        Stack<Commands.Command> _activeCommandStack = new Stack<Splino.Commands.Command>();
        /// <summary>
        /// Gets a stack of commands that have been initialized but not finalized
        /// The idle command always remains at the bototm of this stack
        /// </summary>
        public Stack<Commands.Command> ActiveCommandStack
        {
            get { return _activeCommandStack; }
        }

        Stack<Commands.Command> _viewCommandUndoStack = new Stack<Commands.Command>();
        /// <summary>
        /// Gets the view command undo stack.  Commands popped from this stack return
        /// the viewport to a previous state
        /// </summary>
        public Stack<Commands.Command> ViewCommandUndoStack
        {
            get { return _viewCommandUndoStack; }
        }

        Stack<Commands.Command> _viewCommandRedoStack = new Stack<Commands.Command>();
        /// <summary>
        /// Gets the view command redo stack.  Commands popped from this stack return the
        /// viewport to a state that was previously undone
        /// </summary>
        public Stack<Commands.Command> ViewCommandRedoStack
        {
            get { return _viewCommandRedoStack; }
        }

        Stack<Commands.Command> _objectCommandUndoStack = new Stack<Commands.Command>();
        /// <summary>
        /// Gets the object command undo stack.  Commands popped from this stack return an
        /// object(s) to a previous state
        /// </summary>
        public Stack<Commands.Command> ObjectCommandUndoStack
        {
            get { return _objectCommandUndoStack; }
        }

        Stack<Commands.Command> _objectCommandRedoStack = new Stack<Commands.Command>();
        /// <summary>
        /// Gets the object command redo stack.  Commands popped from this stack return an
        /// object(s) that was previously redone.
        /// </summary>
        public Stack<Commands.Command> ObjectCommandRedoStack
        {
            get { return _objectCommandRedoStack; }
        }

        SplinoWindow _activeWindow;
        /// <summary>
        /// Gets or sets the SplinoPanel that currently has focus
        /// </summary>
        public SplinoWindow ActiveWindow
        {
            get { return _activeWindow; }
            set
            {
                _activeWindow = value;
                _activeCommandStack.Peek().ActiveWindow = _activeWindow;
            }
        }

        Commands.Command _lastCommand;
        /// <summary>
        /// Gets the last command that was executed
        /// </summary>
        public Commands.Command LastCommand
        {
            get
            {
                if (_lastCommand != null)
                {
                    return (Commands.Command)
                        (Activator.CreateInstance(_lastCommand.GetType()));
                }
                else
                    return null;
            }
        }

        private static readonly object padlock = new object();
        private static volatile CommandManager _instance;
        /// <summary>
        /// Gets the singleton instance of the CommandManager
        /// </summary>
        public static CommandManager Instance
        {
            get
            {
                if (_instance == null)
                    lock (padlock)
                    {
                        if (_instance == null)
                            _instance = new CommandManager();
                        _instance.ExecuteCommand(new Commands.Default());
                    }
                return _instance;
            }
        }

        /// <summary>
        /// Gets whether the Command Manager instance has been created
        /// </summary>
        public static bool Initialized { get { return _instance != null; } }

        /// <summary>
        /// Public constructor, taking the parent SplinoForm as an argument
        /// </summary>
        private CommandManager()
        {
            Array.Sort<string>(_commandList);
            _workingCommandList = new string[_commandList.Length];
            Array.Copy(_commandList, _workingCommandList, _commandList.Length);
            for (int i = 0; i < _workingCommandList.Length; i++)
                _workingCommandList[i] = _workingCommandList[i].ToLower();

            _outputText = "Splino Alpha " + SplinoForm.Instance.VersionShort;

            _activeWindow = SplinoForm.Instance.SplinoWindows[0];
        }

        /// <summary>
        /// Displays the CommandPrompt form
        /// </summary>
        public void ShowCommandPrompt()
        {
            if (!_commandPromptActive)
            {
                _commandPrompt = new CommandPrompt();
                _commandPromptActive = true;
                _commandPrompt.TextBoxOutput.Text = _outputText;
                _commandPrompt.Show(SplinoForm.Instance);
            }
            CommandPrompt.TextBoxInput.Focus();
        }

        /// <summary>
        /// Parses user input from the CommmandPrompt
        /// </summary>
        /// <param name="inputCommand"></param>
        public void ProcessCommandPromptInput(string inputCommand)
        {
            if (_activeCommandStack.Peek().ProcessingInput)
            {
                if (!ProcessCommandInputParameters(inputCommand))
                    ProcessCommandCall(inputCommand);
            }
            else
            {
                ProcessCommandCall(inputCommand);
            }
        }

        private bool ProcessCommandInputParameters(string inputCommand)
        {
            object input = null;
            object[] inputs = null;

            string[] commaDelimitedInput = inputCommand.Split(',');
            if (commaDelimitedInput.Length <= 3)
            {

                // Handle numeric input
                try
                {
                    List<float> floatStrings = new List<float>();
                    foreach (string floatString in commaDelimitedInput)
                        floatStrings.Add(float.Parse(floatString));
                    switch (floatStrings.Count)
                    {
                        case 1:
                            float inputFloat = floatStrings[0];
                            input = inputFloat;
                            _activeCommandStack.Peek().ProcessFloat(inputFloat);
                            break;
                        case 2:
                            Vector2 inputVector2 = new Vector2(floatStrings[0], floatStrings[1]);
                            input = inputVector2;
                            _activeCommandStack.Peek().ProcessVector2(inputVector2);
                            break;
                        case 3:
                            Vector3 inputVector3 = new Vector3(floatStrings[0], floatStrings[1], floatStrings[2]);
                            input = inputVector3;
                            _activeCommandStack.Peek().ProcessVector3(inputVector3);
                            break;
                    }
                }

                // Create a list of Splino Objects if input is non numeric
                catch
                {
                    inputs = ProcessObjectNameSelection(commaDelimitedInput);
                }
            }
            else
                inputs = ProcessObjectNameSelection(commaDelimitedInput);

            if (input != null || inputs != null)
            {
                _activeCommandStack.Peek().ProcessInput(input,inputs);
                return true;
            }
            return false;
        }

        private Object[] ProcessObjectNameSelection(string[] commaDelimitedInput)
        {
            List<SplinoObject> splinoObjects = new List<SplinoObject>();
            foreach (string objectString in commaDelimitedInput)
            {
                if (objectString[0] == '\"'
                    && objectString[objectString.Length - 1] == '\"')
                {
                    String trimmedString = objectString;
                    trimmedString = trimmedString.Remove(0, 1);
                    trimmedString = trimmedString.Remove(trimmedString.Length - 1, 1);
                    foreach (SplinoObject splinoObject in
                        SceneManager.Instance.MasterSceneObject.SceneChildren)
                        if (string.Compare(trimmedString, splinoObject.Name, true) == 0)
                            splinoObjects.Add(splinoObject);
                }
            }
            if (splinoObjects.Count > 0)
                return splinoObjects.ToArray();
            return null;
        }

        private void ProcessCommandCall(string inputCommand)
        {
            int commandIndex = Array.BinarySearch<string>(
                _workingCommandList, inputCommand.ToLower());
            if (commandIndex >= 0)
            {
                //try
                //{
                    Type commandType = Type.GetType("Splino.Commands." + _commandList[commandIndex]);
                    Commands.Command newCommand = (Commands.Command)
                    Activator.CreateInstance(commandType);
                    ExecuteCommand(newCommand);
                //}
                //catch
                //{
                //    WriteEchoLine(inputCommand);
                //    WriteErrorLine("Command not implemented.");
                //}
            }
            else
            {
                WriteEchoLine(inputCommand);
                WriteErrorLine("Command or object not found.");
            }
        }

        /// <summary>
        /// Executes a newly initialized Splino command
        /// </summary>
        /// <param name="newCommand"></param>
        public void ExecuteCommand(Commands.Command newCommand)
        {
            if (newCommand.CommandType != Commands.CommandType.SystemCommand)
            {
                if (_activeCommandStack.Peek().CommandType == Commands.CommandType.ObjectCommand
                    && newCommand.CommandType == Commands.CommandType.ObjectCommand)
                {
                    _activeCommandStack.Peek().FinalizeCommand();
                }
                else if (_activeCommandStack.Peek().CommandType == Commands.CommandType.ViewportCommand
                    && newCommand.CommandType == Commands.CommandType.ViewportCommand)
                {
                    _activeCommandStack.Peek().FinalizeCommand();
                }
            }

            bool repeatableCommand = CheckRepeatViewCommands(newCommand);
            if (!repeatableCommand &&
                newCommand.CommandType != Commands.CommandType.SystemCommand)
            {
                WriteEchoLine(newCommand.Name);
                WriteDescriptionLine(newCommand.Description);
                if (newCommand.CommandType == Commands.CommandType.ObjectCommand)
                    _lastCommand = newCommand;
            }
            switch (newCommand.CommandType)
            {
                case Splino.Commands.CommandType.ViewportCommand:
                    if (newCommand.Undoable)
                    {
                        _viewCommandRedoStack.Clear();
                        SplinoForm.Instance.redoViewChangeToolStripMenuItem.Enabled = false;
                    }
                    break;
                case Splino.Commands.CommandType.ObjectCommand:
                    if (newCommand.Undoable)
                    {
                        ObjectCommandRedoStack.Clear();
                        SplinoForm.Instance._redoToolStripMenuItem.Enabled = false;
                        SplinoForm.Instance.RedoMultipleMenuItem.Enabled = false;
                    }
                    break;
            }
            newCommand.Execute();
        }

        /// <summary>
        /// Adds a view command to the view command undo stack
        /// </summary>
        public void AddViewCommandToUndoStack()
        {
            ViewCommandUndoStack.Push(_activeCommandStack.Peek());
            SplinoForm.Instance.undoViewChangeToolStripMenuItem.Enabled = true;
        }
        
        /// <summary>
        /// Adds an object command to the object command undo stack
        /// </summary>
        public void AddObjectCommandToUndoStack()
        {
            _objectCommandUndoStack.Push(_activeCommandStack.Peek());
            SplinoForm.Instance._undoToolStripMenuItem.Enabled = true;
            SplinoForm.Instance.UndoMultipleMenuItem.Enabled = true;
            SplinoForm.Instance._undoToolStripMenuItem.Text = "Undo " + _activeCommandStack.Peek().Name;
        }

        /// <summary>
        /// Undoes a view command, popping it from the view command undo stack
        /// </summary>
        public void UndoViewCommand()
        {
            if (_viewCommandUndoStack.Count > 0)
            {
                bool repeatable = ViewCommandUndoStack.Peek().Repeatable;
                String id = _viewCommandUndoStack.Peek().ID;
                _viewCommandRedoStack.Push(_viewCommandUndoStack.Peek());
                _viewCommandUndoStack.Pop().Undo();
                SplinoForm.Instance.redoViewChangeToolStripMenuItem.Enabled = true;
                if (_viewCommandUndoStack.Count == 0)
                {
                    SplinoForm.Instance.undoViewChangeToolStripMenuItem.Enabled = false;
                    return;
                }
                if (repeatable && id == _viewCommandUndoStack.Peek().ID)
                    UndoViewCommand();
            }
            else
                WriteErrorLine("No view change to undo.");
        }

        /// <summary>
        /// Redoes a view command, popping it from the view command redo stack
        /// </summary>
        public void RedoViewCommand()
        {
            if (_viewCommandRedoStack.Count > 0)
            {
                bool repeatable = _viewCommandRedoStack.Peek().Repeatable;
                String id = _viewCommandRedoStack.Peek().ID;
                ViewCommandUndoStack.Push(_viewCommandRedoStack.Peek());
                _viewCommandRedoStack.Pop().Redo();
                SplinoForm.Instance.undoViewChangeToolStripMenuItem.Enabled = true;
                if (_viewCommandRedoStack.Count == 0)
                {
                    SplinoForm.Instance.redoViewChangeToolStripMenuItem.Enabled = false;
                    return;
                }
                if (repeatable && id == _viewCommandRedoStack.Peek().ID)
                    RedoViewCommand();
            }
            else
                WriteErrorLine("No view change to redo.");
        }

        /// <summary>
        /// Undoes an object command, popping it from the object command undo stack
        /// </summary>
        public void UndoObjectCommand()
        {
            if (_objectCommandUndoStack.Count > 0)
            {
                _objectCommandRedoStack.Push(_objectCommandUndoStack.Peek());
                _objectCommandUndoStack.Pop().Undo();
                SplinoForm.Instance._redoToolStripMenuItem.Enabled = true;
                SplinoForm.Instance._redoToolStripMenuItem.Text = "Redo " + _objectCommandRedoStack.Peek().Name;
                SplinoForm.Instance.RedoMultipleMenuItem.Enabled = true;
                if (_objectCommandUndoStack.Count == 0)
                {
                    SplinoForm.Instance._undoToolStripMenuItem.Enabled = false;
                    SplinoForm.Instance._undoToolStripMenuItem.Text = "Undo";
                    SplinoForm.Instance.UndoMultipleMenuItem.Enabled = false;
                }
                else
                    SplinoForm.Instance._undoToolStripMenuItem.Text = "Undo " + _objectCommandUndoStack.Peek().Name;
            }
            else
                WriteErrorLine("Nothing to undo.");
        }

        /// <summary>
        /// Redoes an object command, popping it from the object command redo stack
        /// </summary>
        public void RedoObjectCommand()
        {
            if (_objectCommandRedoStack.Count > 0)
            {
                _objectCommandUndoStack.Push(_objectCommandRedoStack.Peek());
                _objectCommandRedoStack.Pop().Redo();
                SplinoForm.Instance._undoToolStripMenuItem.Enabled = true;
                SplinoForm.Instance._undoToolStripMenuItem.Text = "Undo " + _objectCommandUndoStack.Peek().Name;
                SplinoForm.Instance.UndoMultipleMenuItem.Enabled = true;
                if (_objectCommandRedoStack.Count == 0)
                {
                    SplinoForm.Instance._redoToolStripMenuItem.Enabled = false;
                    SplinoForm.Instance._redoToolStripMenuItem.Text = "Redo";
                    SplinoForm.Instance.RedoMultipleMenuItem.Enabled = false;
                }
                else
                    SplinoForm.Instance._redoToolStripMenuItem.Text = "Redo " + _objectCommandRedoStack.Peek().Name;
            }
            else
                WriteErrorLine("Nothing to redo.");
        }

        /// <summary>
        /// Writes a line of text on the command prompt
        /// </summary>
        /// <param name="outputString"></param>
        public void WriteEchoLine(string outputString)
        {
            _outputText += "\r\n" + outputString;
            if (CommandPromptActive)
            {
                _commandPrompt.TextBoxOutput.AppendText(outputString);
                ScrollTextBoxToBottom();
            }
        }

        /// <summary>
        /// Writes a command's description on the command prompt
        /// </summary>
        /// <param name="outputString"></param>
        public void WriteDescriptionLine(string outputString)
        {
            _outputText += " - " + outputString;
            if (CommandPromptActive)
            {
                _commandPrompt.TextBoxOutput.Text = _outputText;
                ScrollTextBoxToBottom();
            }
        }

        /// <summary>
        /// Writes an error message on the command prompt
        /// </summary>
        /// <param name="outputString"></param>
        public void WriteErrorLine(string outputString)
        {
            _outputText += "\r\n\tERROR: " + outputString;
            _outputText += "\r\n\tPress F1 for help using the command prompt.";
            if (CommandPromptActive)
            {
                _commandPrompt.TextBoxOutput.Text = _outputText;
                ScrollTextBoxToBottom();
            }
        }

        /// <summary>
        /// Writes a command's usage on the command prompt
        /// </summary>
        /// <param name="outputString">A string description of how to use the command</param>
        public void WriteUsageLine(string outputString)
        {
            if (CommandPromptActive)
                _commandPrompt.Label.Text = outputString;
        }

        /// <summary>
        /// Writes output information from a command after it has been executed
        /// </summary>
        /// <param name="outputString">A string representing output results from the command</param>
        public void WriteOutputLine(string outputString)
        {
            _outputText += "\r\n\tSplino Output: " + outputString;
            if (_commandPromptActive)
            {
                _commandPrompt.TextBoxOutput.Text = _outputText;
                ScrollTextBoxToBottom();
            }
        }

        /// <summary>
        /// Moves the command prompt cursor or carat to the bottom
        /// of the output textbox
        /// </summary>
        public void ScrollTextBoxToBottom()
        {
            _commandPrompt.TextBoxOutput.SelectAll();
            _commandPrompt.TextBoxOutput.ScrollToCaret();
        }

        /// <summary>
        /// Returns true if the last view command in the undo stack is
        /// repeatable (undoable for multiple consecutive executions of that command)
        /// </summary>
        /// <param name="newCommand">A newly instantiated view command</param>
        /// <returns></returns>
        public bool CheckRepeatViewCommands(Commands.Command newCommand)
        {
            if (newCommand.Repeatable)
            {
                if (_viewCommandUndoStack.Count > 0 &&
                    _viewCommandUndoStack.Peek().Name == newCommand.Name)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Shows the Undo Multiple dialog, configuring the form and then
        /// adding the items from the undo stack to the form's list view
        /// </summary>
        public void UndoMultiple()
        {
            if (_objectCommandUndoStack.Count > 0)
            {
                UndoRedoMultiple undoRedoMultiple = new UndoRedoMultiple();
                undoRedoMultiple.Text = "Undo Multiple";
                undoRedoMultiple.UndoRedoButton.Text = "Undo";
                foreach (Commands.Command command in _objectCommandUndoStack)
                {
                    undoRedoMultiple.ListView.Items.Add(command.Name);
                }
                undoRedoMultiple.ShowDialog(SplinoForm.Instance);
            }
            else
                WriteErrorLine("Nothing to undo.");
        }

        /// <summary>
        /// Shows the Redo Multiple dialog, configuring the form and then
        /// adding the items from the redo stack to the form's list view
        /// </summary>
        public void RedoMultiple()
        {
            if (_objectCommandRedoStack.Count > 0)
            {
                UndoRedoMultiple undoRedoMultiple = new UndoRedoMultiple();
                undoRedoMultiple.Text = "Redo Multiple";
                undoRedoMultiple.UndoRedoButton.Text = "Redo";
                foreach (Commands.Command command in _objectCommandRedoStack)
                    undoRedoMultiple.ListView.Items.Add(command.Name);
                undoRedoMultiple.ShowDialog(SplinoForm.Instance);
            }
            else
                WriteErrorLine("Nothing to redo.");
        }

        /// <summary>
        /// Closes the Command Prompt if it is open and reinstantiates the
        /// COmmand Manager
        /// </summary>
        public void ClearCommandManager()
        {
            if (_instance.CommandPromptActive)
                _instance.CommandPrompt.Close();
            _instance = new CommandManager();
            _instance.ExecuteCommand(new Commands.Default());
        }

        /// <summary>
        /// Hooks a Splino Window into the mouse events of the active commands
        /// </summary>
        /// <param name="splinoWindow">The Splino Window to register</param>
        public void RegisterSplinoWindow(SplinoWindow splinoWindow)
        {
            foreach (Commands.Command command in _activeCommandStack)
            {
                splinoWindow.PresentationPanel.MouseMove += new MouseEventHandler(command.MouseMove);
                splinoWindow.PresentationPanel.MouseEnter += new EventHandler(command.MouseEnter);
                splinoWindow.PresentationPanel.MouseLeave += new EventHandler(command.MouseLeave);
                splinoWindow.PresentationPanel.MouseDown += new MouseEventHandler(command.MouseDown);
                splinoWindow.PresentationPanel.MouseUp += new MouseEventHandler(command.MouseUp);
                splinoWindow.PresentationPanel.MouseWheel += new MouseEventHandler(command.MouseWheel);
            }
        }
    }
}
